package edu.mse.camel.client.posterboard.ui.editor;

import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.gef.commands.Command;
import org.eclipse.gef.commands.CommandStack;
import org.eclipse.gef.commands.CompoundCommand;
import org.eclipse.jface.dialogs.ErrorDialog;
import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.MessageBox;
import org.eclipse.swt.widgets.Shell;

import edu.mse.camel.client.eventbus.pubsub.EventManager;
import edu.mse.camel.client.events.EventIDs;
import edu.mse.camel.client.events.server.SemaphoreEvent;
import edu.mse.camel.client.events.server.SemaphoreEvent.SemaphoreOperation;
import edu.mse.camel.client.posterboard.ui.emflistener.CamelChangeRecorder2;
import edu.mse.camel.client.session.team.ITeamMember;
import edu.mse.camel.client.session.team.TEAMMEMBER_STATUS;
import edu.mse.camel.client.session.team.TeamList;
import edu.mse.camel.client.whiteboard.gef.ui.commands.AbstractWhiteboardCommand;

public class CamelCommandStack extends CommandStack {

	private CamelModelUpdateSerialNumber serialNumber; 
	private PosterBoardModelManager pbModelManager;
	
	public CamelCommandStack(PosterBoardModelManager modelManager, CamelModelUpdateSerialNumber serialNumber) {
		super();
		this.pbModelManager = modelManager;
		this.serialNumber = serialNumber;
	}
	
	private CamelChangeRecorder2 PreDo () {
		CamelChangeRecorder2 recorder = new CamelChangeRecorder2(pbModelManager.getResourceSet());
		recorder.beginRecording();
		return recorder;
	}
	
	private void PostDo (CamelChangeRecorder2 recorder, Command command) {
		if(recorder != null) {
			if(recorder.IsRecording()) {
				recorder.publish(serialNumber.getSerialNumberAndThenInc(), command.getLabel());
				recorder.dispose();
			}
		}
		
		if(isWhiteboardCommand(command)) {
			// if this is a white board command, release the token we have acquired
			releaseWhiteboardToken(command);
		}
	}
	
	@Override
	public void redo() {
		Command command = this.getUndoCommand();
		if(!canExecute(command)) {
			return;
		}
		CamelChangeRecorder2 recorder = PreDo();
		super.redo();
		PostDo(recorder, command);
	}

	@Override
	public void undo() {
		Command command = this.getUndoCommand();
		if(!canExecute(command)) {
			return;
		}
		CamelChangeRecorder2 recorder = PreDo();
		super.undo();
		PostDo(recorder, command);
	}
	
	private boolean inSession() {
		if (TeamList.getInstance().getMyStatus() == TEAMMEMBER_STATUS.SESSION) {
			return true;
		}
		return false;
	}
	
	private boolean isWhiteboardCommand (Command command) {
		if(command instanceof AbstractWhiteboardCommand) {
			return true;
		}
		else if (command instanceof CompoundCommand) {
			CompoundCommand cc = (CompoundCommand)command;
			Command child = (Command) cc.getCommands().get(0);
			if(child instanceof AbstractWhiteboardCommand) {
				return true;
			}
		}
		return false;
	}
	private boolean acquireWhiteboardToken(Command command) {
		// if this is a white board command, grab the token instead of checking for facilitator role
		SemaphoreEvent event= new SemaphoreEvent(SemaphoreOperation.TryAcquire);
		
		// retry for only 300 times
		for(int i = 0; (i<300) && (! event.isOpSuccessful());++i) {
			event.setId(0);
			event.setIdValid(true);
			EventManager.getInstance().dispatchEventSynchronous(event, true);
			
			if(! event.isOpSuccessful()) {
				// if not locked
				try {
					// wait for a small amount of time before retry
					Thread.sleep(100);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		}
		if(!event.isOpSuccessful()) {
			ErrorDialog ed = new ErrorDialog(new Shell(),
					"Warning",
					"Cannot grab the whiteboard token within time limit", new Status(
							IStatus.WARNING, "Camel",
							"Cannot grab the whiteboard token within time limit"),
					IStatus.WARNING);
			ed.open();
		}
		return event.isOpSuccessful();
	}
	
	private void releaseWhiteboardToken(Command command) {
		// if this is a white board command, grab the token instead of checking for facilitator role
		// if this is a white board command, grab the token instead of checking for facilitator role
		SemaphoreEvent event= new SemaphoreEvent(SemaphoreOperation.Release);
		event.setId(0);
		event.setIdValid(true);
		EventManager.getInstance().dispatchEventSynchronous(event, true);
	}

	private boolean canExecute(Command command) {
		if(inSession()) {
			if(isWhiteboardCommand(command)) {
				// if this is a whiteboard command, grab the token instead of checking for facilitator role
				return acquireWhiteboardToken (command);
			}
			else {
				boolean isFacilitator = TeamList.getInstance().getMe().isFacilitator();
				if(isFacilitator) {
					return true;
				}
			}
		}
		MessageBox messageBox = new MessageBox(new Shell(), SWT.OK);
		messageBox.setText("You are not the facilitator");
		messageBox.setMessage("Cannot modify the model if you are not the facilitator");
		messageBox.open();
		return false;
	}

	public void execute(Command command) {
		
		if(!canExecute(command)) {
			return;
		}
		
		CamelChangeRecorder2 recorder = PreDo();
		
		super.execute(command);
		
		PostDo(recorder, command);
	}

}
